﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using TinySteamWrapper.Exceptions;
using TinySteamWrapper.Steam;

namespace TinySteamWrapper
{
    public sealed class SteamManager
    {

        #region Variables

        private static String steamBaseURL = "http://api.steampowered.com/";

        private static IDictionary<int, SteamApp> apps;

        private static bool gamesAreLoaded = false;

        /** <summary>Getter/Setter for the Steam API-Key (32 digits and letters).</summary>   
         *  <remarks>Certain API-calls require the key, others (such as gettings the app-dictionary) don't. Get your own key here: http://steamcommunity.com/dev/apikey</remarks>
         */
        public static String SteamAPIKey { get; set; }

        #endregion

        #region Constructor
        static SteamManager()
        {
            apps = new Dictionary<int, SteamApp>();
            ConstructorAsync();
        }

        private static async void ConstructorAsync()
        {
            String response = await GetSteamResponse("ISteamApps/GetAppList/v0002/", false);
            SteamAppListWrapper result = JsonConvert.DeserializeObject<SteamAppListWrapper>(response);
            List<JsonApp> resultApps = result.applist.apps;

            if (resultApps != null)
            {
                apps.Clear();
                foreach (JsonApp app in resultApps)
                {
                    apps.Add(app.appid, new SteamApp(app));
                }
                gamesAreLoaded = true;
            }
        }

        #endregion

        #region Private Methods

        private static async Task GamesLoaded()
        {
            while (!gamesAreLoaded)
            {
                await Task.Delay(30);
            }
        }

        private static DateTime UnixTimeStampToDateTime(long unixTimeStamp)
        {
            DateTime dtDateTime = new DateTime(1970,1,1,0,0,0,0,System.DateTimeKind.Utc);
            dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime();
            return dtDateTime;
        }

        private static TimeSpan ToTimeSpan(long time)
        {
            long ticks = time * 60 * 10000000;
            return new TimeSpan(ticks);
        }

        private static async Task<String> GetSteamResponse(String controller, bool addCredentials)
        {
            String url = steamBaseURL + controller;
            if (addCredentials)
            {
                if (SteamAPIKey != null && !SteamAPIKey.Equals(""))
                {
                    url += "&key=" + SteamAPIKey + "&format=json";
                }
                else
                {
                    throw new CredentialsMissingException();
                }
            }

            var client = new HttpClient();
            var response = await client.GetAsync(url);
            var responseText = await response.Content.ReadAsStringAsync();
            return responseText;
        }

        private static SteamProfile Convert(JsonProfile sp)
        {
            return new SteamProfile()
            {
                Avatar = sp.avatar,
                AvatarFull = sp.avatarfull,
                AvatarMedium = sp.avatarmedium,
                CityID = sp.loccityid,
                CommunityVisibilityState = sp.communityvisibilitystate,
                CountryCode = sp.loccountrycode,
                CurrentGame = GetApp(sp.gameid),
                CurrentGameExtraInfo = sp.gameextrainfo,
                CurrentGameServerIP = sp.gameserverip,
                CurrentGameServerSteamID = sp.gameserversteamid,
                CurrentLobbySteamID = sp.lobbysteamid,
                LastLogOff = UnixTimeStampToDateTime(sp.lastlogoff),
                PersonaName = sp.personaname,
                PersonaState = sp.personastate,
                PersonaStateFlags = sp.personastateflags,
                PrimaryClanID = sp.primaryclanid,
                ProfileState = sp.profilestate,
                ProfileUrl = sp.profileurl,
                RealName = sp.realname,
                StateCode = sp.locstatecode,
                SteamID = sp.steamid,
                TimeCreated = UnixTimeStampToDateTime(sp.timecreated)
            };
        }

        private static SteamApp GetApp(int appID)
        {
            if (apps.ContainsKey(appID))
            {
                return apps[appID];
            }
            return null;
        }

        #endregion

        #region Public Methods

        /** <summary>Returns the Steam-ID to a given Steam username or 0 if none is found.</summary> 
         * <exception cref="TinySteamWrapper.Exceptions.CredentialsMissingException">Thrown when no SteamAPIKey has been set</exception>
         */
        public static async Task<long> GetSteamIDByName(String name)
        {
            String response = await GetSteamResponse("ISteamUser/ResolveVanityURL/v1?vanityurl=" + name, true);
            Response<JsonID> result = JsonConvert.DeserializeObject<Response<JsonID>>(response);
            return result.response.steamid;
        }


        /** <summary>Returns a list of SteamProfiles for given SteamID's. The SteamProfiles implement INotifyPropertyChanged and can be used for data-binding.</summary>   
         * <exception cref="TinySteamWrapper.Exceptions.CredentialsMissingException">Thrown when no SteamAPIKey has been set</exception>
         */
        public static async Task<IList<SteamProfile>> GetSteamProfilesByIDs(long[] steamIDs)
        {
            String combinedIDs = "";
            foreach (long id in steamIDs)
            {
                combinedIDs += id + ",";
            }
            combinedIDs = combinedIDs.Substring(0, combinedIDs.Length - 1);

            String response = await GetSteamResponse("ISteamUser/GetPlayerSummaries/v0002/?steamids=" + combinedIDs, true);

            Response<JsonProfileList> result = JsonConvert.DeserializeObject<Response<JsonProfileList>>(response);

            List<SteamProfile> profiles = new List<SteamProfile>();
            await GamesLoaded(); // Because we need to resolve the game from the dictionary
            foreach (JsonProfile steamProfile in result.response.players)
            {
                profiles.Add(Convert(steamProfile));
            }

            return profiles;
        }


        /** <summary>Loads (or reloads) the list of owned games for a given SteamProfile.</summary> 
         * <exception cref="TinySteamWrapper.Exceptions.CredentialsMissingException">Thrown when no SteamAPIKey has been set</exception>
         */
        public static async Task LoadGamesForProfile(SteamProfile profile)
        {
            if (profile != null)
            {
                await GamesLoaded();

                String response = await GetSteamResponse("IPlayerService/GetOwnedGames/v0001/?steamid=" + profile.SteamID, true);
                Response<JsonGameList> result = JsonConvert.DeserializeObject<Response<JsonGameList>>(response);
                var profileGames = result.response.games;

                if (profileGames != null)
                {
                    profile.Games.Clear();
                    foreach (JsonGame game in profileGames.OrderByDescending(t => t.playtime_forever))
                    {
                        profile.Games.Add(new SteamProfileGame(GetApp(game.appid), ToTimeSpan(game.playtime_forever)));
                    }
                }
            }
        }


        /** <summary>Returns a SteamProfile for a given SteamID or null, if none is found.</summary> 
         * <exception cref="TinySteamWrapper.Exceptions.CredentialsMissingException">Thrown when no SteamAPIKey has been set</exception>
         */
        public static async Task<SteamProfile> GetSteamProfileByID(long steamID)
        {
            String response = await GetSteamResponse("ISteamUser/GetPlayerSummaries/v0002/?steamids=" + steamID, true);
            Response<JsonProfileList> result = JsonConvert.DeserializeObject<Response<JsonProfileList>>(response);
            await GamesLoaded();
            if (result.response.players.Count > 0)
            {
                return Convert(result.response.players[0]);
            }
            return null;
        }


        /** <summary>Returns a dictionary with all currently available apps on Steam.</summary>
         *  <remarks>In 2014 there were ~11500 apps registered</remarks> 
         */     
        public static async Task<IDictionary<int, SteamApp>> GetFullAppDictionary()
        {
            await GamesLoaded();
            return apps;
        }

        /** <summary>Updates the given SteamProfiles' properties (INotifyChangedProperty is raised on each change)</summary>
         * <exception cref="TinySteamWrapper.Exceptions.CredentialsMissingException">Thrown when no SteamAPIKey has been set</exception>
         */
        public static async Task UpdateProfiles(IList<SteamProfile> profiles)
        {
            var ids = profiles.Select(t => t.SteamID).ToArray();
            var newProfiles = await GetSteamProfilesByIDs(ids);

            if (newProfiles != null)
            {
                foreach (SteamProfile profile in newProfiles)
                {
                    var oldProfile = profiles.Where(p => p.SteamID == profile.SteamID).First();
                    if (oldProfile != null)
                    {
                        oldProfile.Avatar = profile.Avatar;
                        oldProfile.AvatarFull = profile.AvatarFull;
                        oldProfile.AvatarMedium = profile.AvatarMedium;
                        oldProfile.CityID = profile.CityID;
                        oldProfile.CommunityVisibilityState = profile.CommunityVisibilityState;
                        oldProfile.CountryCode = profile.CountryCode;
                        oldProfile.CurrentGame = profile.CurrentGame;
                        oldProfile.CurrentGameExtraInfo = profile.CurrentGameExtraInfo;
                        oldProfile.CurrentGameServerIP = profile.CurrentGameServerIP;
                        oldProfile.CurrentGameServerSteamID = profile.CurrentGameServerSteamID;
                        oldProfile.CurrentLobbySteamID = profile.CurrentLobbySteamID;
                        oldProfile.LastLogOff = profile.LastLogOff;
                        oldProfile.PersonaName = profile.PersonaName;
                        oldProfile.PersonaState = profile.PersonaState;
                        oldProfile.PersonaStateFlags = profile.PersonaStateFlags;
                        oldProfile.PrimaryClanID = profile.PrimaryClanID;
                        oldProfile.ProfileState = profile.ProfileState;
                        oldProfile.ProfileUrl = profile.ProfileUrl;
                        oldProfile.RealName = profile.RealName;
                        oldProfile.StateCode = profile.StateCode;
                    }
                }
            }
        }

        #endregion
    }
}
